package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.constant.ProductConstant;
import com.atguigu.gulimall.product.dao.CategoryBrandRelationDao;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import com.atguigu.gulimall.product.vo.Catelog3Vo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryBrandRelationDao dao;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    @Cacheable(value = "category",key = "#root.methodName")
    public List<CategoryEntity> getCategoryLevel1() {
     return   baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>()
               .eq(CategoryEntity::getParentCid,0));
    }

    /**
     * 方法改造，我们要走缓存
     * @return
     */
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJsonV2() {
        //第一步我们先到缓存里面去拿
        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)){
            //如果你的catalogJson是空的，就代表redis里面没有,不得不到库里去查
            Map<String, List<Catelog2Vo>> map = this.getCatalogJson();
            //得到这个map后你就需要把这个map放到redis里面去.先把map转成一个string
            String s = JSON.toJSONString(map);
            //把redis里面也放了一份
            stringRedisTemplate.opsForValue().set("catalogJson",s);
            return map;
        }
        //如果redis里面有我就把catalogJson字符串转成一个对象
        Map<String, List<Catelog2Vo>> parseObject = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return parseObject;
    }

    /**
     *  这个版本使用
     * @return
     */
    @Override
    @Cacheable(value = "category",key = "#root.methodName")
    public Map<String, List<Catelog2Vo>> getCatalogJsonV3() {
        Map<String, List<Catelog2Vo>> map = this.getCatalogJson();
        return map;
    }
    /**
     * 这个方法实现的思路就是：因为你要组装一级分类id为key，该一级分类下的二级分类的集合为value的map
     * 当然你这个二级分类，里面是有三级分类的
     * @return
     */
    @Override
    @Cacheable(value = "category",key = "#root.methodName")
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //为了避免循环查库，你先把所有分类都插出来，后面组装分类就在所有分类里面过滤
        //categoryList就一次性拿出来所有分类
        List<CategoryEntity> categoryList = baseMapper.selectList(null);
        //第二步就是拿到所有的一级分类 你当然可以这样写 List<CategoryEntity> categoryLevel1 = this.getCategoryLevel1();
       //但是要查数据库，所以我们要在所有分类里面去过滤
        List<CategoryEntity> categoryLevel1List = fliterChildCategoryByParentId(categoryList, 0L);
        //现在我们的目标是，以一级分类id为key，以他孩子的集合为value来构造一个map，当然我们这里就可以利用集合转map的思想
        Map<String, List<Catelog2Vo>> map = categoryLevel1List.stream().collect(Collectors.toMap((k) -> k.getCatId().toString(), v -> {
            //下面做的所有事情都是找到该一级分类的Catelog2Vo格式孩子集合
            //v的值为这个cateId对应的孩子的集合 为后续流式装成Catelog2Vo做准备
            List<CategoryEntity> categoryLevel2List = fliterChildCategoryByParentId(categoryList, v.getCatId());
            //当二该一级分类下的二级分类不为空时该k对应的v,否则返回null
            if (categoryLevel2List !=null && categoryLevel2List.size()>0){
                //走这里就证明，该一级分类下面有二级分类,你现在需要把category装成Catelog2Vo
                List<Catelog2Vo> Catelog2VoList = categoryLevel2List.stream().map((Level2) -> {
                    //该二级分类下的三级分类
                    List<CategoryEntity> categoryLevel3List = fliterChildCategoryByParentId(categoryList, Level2.getCatId());
                    List<Catelog3Vo> catelog3VoList =null;
                    //该二级分类有三级分类的情况下，我才会有装成catelog3vo的操作
                    if (categoryLevel3List !=null && categoryLevel3List.size()>0){
                        catelog3VoList = categoryLevel3List.stream().map((categoryLevel3) -> {
                            Catelog3Vo catelog3Vo = new Catelog3Vo(Level2.getCatId().toString(), categoryLevel3.getCatId().toString(), categoryLevel3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                    }
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), catelog3VoList, Level2.getCatId().toString(), Level2.getName());
                    return catelog2Vo;
                }).collect(Collectors.toList());
                return Catelog2VoList;
            }else {
                //当二该一级分类下的二级分类不为空时该k对应的v,否则返回null
                return null;
            }
        }));
        return map;
    }



    /**
     * 该方法的作用就是从所有分类中，找出指定分类的子分类
     * @param categoryList
     * @param parentId
     * @return
     */
    @Override
    public List<CategoryEntity> fliterChildCategoryByParentId(List<CategoryEntity> categoryList, Long parentId) {
        return categoryList.stream().filter((item) -> {
            return item.getParentCid() == parentId;
        }).collect(Collectors.toList());
    }




    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    /**
     * 该方法对树形结构的生成要仔细看
     */
    public List<CategoryEntity> listWithTree() {
        //查出所有三级分类目录，并组成父子树形结构
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //(categoryEntity) -> {
        //            return categoryEntity.getParentCid() == 0;
        //categoryEntity表示该集合中的每个元素，{}表示每个元素都执行这个操作
        List<CategoryEntity> level1Menus = entities.stream().filter((categoryEntity) -> {
            //满足父节点的才会被保留
            return categoryEntity.getParentCid() == 0;
        }).sorted((menu1, menu2) -> {
            //给了一个升降序的值，这个点要重新搞一下，细节到咋排是升序，咋排是降序
            //被注释掉的写法，没有考虑到sort可能为空的情况
            //return menu1.getSort()-menu2.getSort();
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).map((menu) -> {
            //给该流对象的每个元素都作用花括号里的函数，这里就是给该分类挂上它的子分类
            menu.setChildren(getCurrentMenuChildren(menu, entities));
            return menu;
        }).collect(Collectors.toList());
        return level1Menus;
    }

    @Override
    public void removeByCatids(List<Long> asList) {
        //TODO 这里要先判断被删除的菜单有没有关联引用
        baseMapper.deleteBatchIds(asList);
    }

    /**
     * 该方法是通过分类Id拿到该分组所属的分类路径
     *
     * @param catelogId 分类id
     * @return
     */
    @Override
    public Long[] findCurrentcatelogIdOfPath(Long catelogId) {
        //定义一个list集合用来收集分类路径，最终需要把他转化为一个long型数组
        List<Long> categoryIdsList = new ArrayList<>();
        categoryIdsList=findThisCategoryParentPath(catelogId,categoryIdsList);
        //把集合里面的元素逆序排列
        Collections.reverse(categoryIdsList);
        return categoryIdsList.toArray(new Long[categoryIdsList.size()]);
    }

    @Override
    @Transactional
    @CacheEvict(value = "category",allEntries = true)//用了一个失效模式，一旦更新删除redis中的缓存
    public void updateDetail(CategoryEntity category) {
        //首先把这个实体类对应的表给给更新了
        baseMapper.updateById(category);
        //因为在pms_category_brand_relation表中
        //是有catelog_id以及catelog_name，你更新冗余字段catelog_name
        if (!StringUtils.isEmpty(category.getName())){
            //然后就要更涉及到的冗余字段表给更了
            dao.updateDetails(category);
        }
    }


    /**
     * 该方法就是递归找到分类的父分类，父分类的父分类，并把他们的id放在集合中
     * @param currentCid
     * @param categoryIdsList
     * @return
     */
    private List<Long> findThisCategoryParentPath(Long currentCid, List<Long> categoryIdsList) {
        //在category里面是没有id为0的记录的，而很多category的父母是0，只是表示它是一级分类。
        //如果没有下写下面的if在递归中会发生空指针异常
        if (currentCid== ProductConstant.CategoryLevel.CATEGORY_LEVEL_ONE.getCode()){
            return categoryIdsList;
        }
        categoryIdsList.add(currentCid);
        CategoryEntity categoryEntity = baseMapper.selectById(currentCid);
        Long parentCid = categoryEntity.getParentCid();
        if (parentCid != null) {
            return findThisCategoryParentPath(parentCid, categoryIdsList);
        }
        return categoryIdsList;
    }

    /**
     * @param currentMenu 当前需要填充孩子节点的Menu
     * @param menus       所有的Menu，通过它找到当前menu的孩子节点
     * @return
     */
    private List<CategoryEntity> getCurrentMenuChildren(CategoryEntity currentMenu, List<CategoryEntity> menus) {
        List<CategoryEntity> childrenOfCurrentMenu = menus.stream().filter((menu) -> {
            //只保留currentMenu的孩子
            return menu.getParentCid() == currentMenu.getCatId();
        }).sorted((menu1, menu2) -> {
            //return menu1.getSort() - menu2.getSort();
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).map((menu) -> {
            //将currentMenu过滤并排序好的孩子节点设置上它的孩子（也就是给孩子设置上孩子）
            menu.setChildren(getCurrentMenuChildren(menu, menus));
            return menu;
        }).collect(Collectors.toList());
        return childrenOfCurrentMenu;
    }
}